﻿using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Diagnostics;

namespace par4log
{
    public enum MSG_LEVEL { ERROR = 4, INFO = 1, FATAL = 5, WARNING = 3, DEBUG = 2 }
    public class Logger
    {
        private static volatile Logger instance;
        private static object syncRoot = new Object();
        private Mutex mut = new Mutex();
        private string startFilePath;
        private long fileMaxSize;
        private StreamWriter sw;
        private bool append;
        private int fileNum;
        private MSG_LEVEL minLevel;
        /// <summary>
        /// 
        /// </summary>
        /// <param name="dirPath">Path to the directory you want to be a log storage</param>
        /// <param name="overwrite">The parameter that shows if you want to rewrite the file if it's already created</param>
        /// <param name="fileMaxSize">The maximum size of file. After file becomes larger than this parameter the new file will be created and log will be put there</param>
        /// <param name="append">The parameter shows if we are to add data to file instead of overwriting</param>
        /// <param name="minLevel">The lowest level which will be written to the log </param>
        private Logger(string startFilePath, bool overwrite, long fileMaxSize, bool append, MSG_LEVEL minLevel)
        {
            this.startFilePath = startFilePath;
            this.append = append;
            fileNum = 0;
            this.fileMaxSize = fileMaxSize;
            this.minLevel = minLevel;
            if (File.Exists(startFilePath))
            {
                var fi = new FileInfo(startFilePath);
                if (fi.IsReadOnly && !overwrite)
                    throw new UnauthorizedAccessException("Can't create file in this folder. It's already exist and is readonly");
            }
            else { File.Create(@startFilePath); }
            sw = new StreamWriter(startFilePath, append);
        }

        public static Logger GetInstance(string startFilePath, bool overwrite, long fileMaxSize, bool append, MSG_LEVEL minLevel)
        {
            if (instance == null)
            {
                lock (syncRoot)
                {
                    if (instance == null)
                        instance = new Logger(startFilePath, overwrite, fileMaxSize, append, minLevel);
                }
            }
            return instance;
        }

        ~Logger()
        {
            try
            {
                sw.WriteLine("Closing the app");
                sw.Close();
            }
            catch (Exception e)
            {
                e.GetType();
            }
        }

        public void Log(MSG_LEVEL msgLevel, string text)
        {
            if (msgLevel < minLevel)
                return;
            // Wait until it is safe to enter.
            mut.WaitOne();

            var stackTrace = new StackTrace();
            var callerName = stackTrace.GetFrame(1).GetMethod().Name;

            sw.WriteLine(GetString(msgLevel) + " " + DateTime.Now.ToString() + " " + text + " from:" + callerName);

            if (sw.BaseStream.Length > fileMaxSize)
            {
                sw.Close();
                var newFileName = "";
                if (++fileNum < 10)
                    newFileName = "_0" + fileNum.ToString();
                else newFileName = "_" + fileNum.ToString();
                sw = new StreamWriter(startFilePath + newFileName , append);
            }
            // Release the Mutex.
            mut.ReleaseMutex();
        }

        /// <summary>
        /// Method that returns the string description of message level
        /// </summary>
        /// <param name="msgLevel">message level MSG_LEVEL</param>
        /// <returns>string description of message level</returns>
        private string GetString(MSG_LEVEL msgLevel)
        {
            switch(msgLevel)
            {
                case MSG_LEVEL.DEBUG: return "DEBUG";
                case MSG_LEVEL.ERROR: return "ERROR";
                case MSG_LEVEL.FATAL: return "FATAL";
                case MSG_LEVEL.INFO: return "INFO";
                case MSG_LEVEL.WARNING: return "WARNING";
            }
            return "";
        }

    }
}
